/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.esdc.librarysystem.manager;

import com.esdc.librarysystem.exception.HasOutstandingLoan;
import com.esdc.librarysystem.exception.LoanLimitException;
import com.esdc.librarysystem.datastate.ReservationState;
import com.esdc.librarysystem.exception.OnReservedException;
import com.esdc.librarysystem.datastate.BookState;
import com.esdc.librarysystem.entity.BookEntity;
import com.esdc.librarysystem.entity.FineEntity;
import com.esdc.librarysystem.entity.MemberEntity;
import com.esdc.librarysystem.entity.ReservationEntity;
import com.esdc.librarysystem.exception.NotExistException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Set;
import javax.ejb.Stateful;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

/**
 *
 * @author shou1dwe
 */
@Stateful
public class LoanManagerBean implements LoanManagerRemote {
    @PersistenceContext
    EntityManager em;

    private static final int LOAN_ALLOWED_DAYS = 14;
    private static final int RENEW_ALLOWED_DAYS = 14;
    private static final int LOAN_LIMIT = 6;

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public boolean newLoan(long bookId, long memberId) throws NotExistException, LoanLimitException, HasOutstandingLoan{
        MemberEntity member = em.find(MemberEntity.class,memberId);
        em.refresh(member);
        if(member == null){
            throw new NotExistException("Member not exists");
        }

        BookEntity book = em.find(BookEntity.class,bookId);
        if(book == null){
            throw new NotExistException("Book not exists");
        }

        if(member.getBooks()!=null && member.getBooks().size()>=LOAN_LIMIT){
            throw new LoanLimitException(String.format("Member has borrowed %d book",LOAN_LIMIT));
        }

        if(member.getFine()!=null && member.getFine().getOwing()>0){
            throw new HasOutstandingLoan("Member has outstanding fine");
        }

        //TODO book is on loan: throws exception
        Date currentDate = new Date();
        book.setLoanDate(currentDate);
        book.setDueDate(calculateDueData(currentDate, LOAN_ALLOWED_DAYS));

        Set<BookEntity> books = member.getBooks();
        books.add(book);
        member.setBooks(books);

        book.setMember(member);

        em.flush();
        return true;
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void endLoan(long bookId) throws NotExistException {
        BookEntity book = em.find(BookEntity.class, bookId);
        if(book == null){
            throw new NotExistException("Book not exists");
        }
        System.out.println("id: " + book.getId());

        MemberEntity member = book.getMember();
        if(member == null){
            throw new NotExistException("Book is not associated with any member");
        }
        
        Date today = new Date();
        em.refresh(book);
        Date dueDay = book.getDueDate();
        if(dueDay.before(today)){
            int fineAmount = (int)( (today.getTime() - dueDay.getTime()) / (1000 * 60 * 60 * 24) ) * 2;
            
            FineEntity fine = member.getFine();
            if(fine==null){
                fine = new FineEntity();
                fine.create(fineAmount);
                em.persist(fine);
            }else{
                fine.setOwing(fine.getOwing()+fineAmount);
            }
            member.setFine(fine);
            em.flush();
        }

        book.setDueDate(null);
        book.setLoanDate(null);
        book.setMember(null);
        member.getBooks().remove(book);
        em.flush();
    }

    public void renewLoanRestricted(Long[] bookId, long memberId) throws NotExistException, OnReservedException, HasOutstandingLoan{
        MemberEntity member = em.find(MemberEntity.class, memberId);
        if(member == null){
            throw new NotExistException("Member not exists");
        }
        
        if(member.getFine()!=null && member.getFine().getOwing()>0){
            throw new HasOutstandingLoan("Member has outstanding fine");
        }

        renewLoan(bookId);
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void renewLoan(Long[] bookId) throws NotExistException, OnReservedException{
        System.out.println("Renewal request received.");
        for (Long id : bookId) {
            BookEntity book = em.find(BookEntity.class, id);
            if (book == null) {
                em.clear();
                throw new NotExistException("Book not exists");
            }
            if (book.getReservations().size() != 0) {
                em.clear();
                throw new OnReservedException(String.format("Book %s is reserved", bookId.toString()));
            }
            Date today = new Date();
            book.setLoanDate(today);
            book.setDueDate(calculateDueData(today, RENEW_ALLOWED_DAYS));
        }
        em.flush();
    }

    private Date calculateDueData(Date loanDate, int daysAllowed) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(loanDate);
        calendar.add(Calendar.DAY_OF_MONTH, daysAllowed);
        Date dueDate = new Date(calendar.getTimeInMillis());
        return dueDate;
    }

    public List<BookState> returnLoans(long memberId) throws NotExistException {
        MemberEntity member = em.find(MemberEntity.class, memberId);
        if(member == null){
            throw new NotExistException(String.format("LoanManagerBean:returnLoans: Member (ID: %d) not exists", memberId));
        }
        Set<BookEntity> loanEntities = member.getBooks();
        List<BookState> loans = new ArrayList();

        for(BookEntity bookEntity : loanEntities){
            loans.add(new BookState(bookEntity.getId(),
                                    bookEntity.getTitle().getTitle(),
                                    bookEntity.getLoanDate(),
                                    bookEntity.getDueDate()));
        }
        
        return loans;
    }

    public List returnReservations(long memberId) throws NotExistException {
       MemberEntity member = em.find(MemberEntity.class, memberId);
        if(member == null){
            throw new NotExistException("Member not exists");
        }

        Set<ReservationEntity> reservationEntities = member.getReservations();
        System.out.println(reservationEntities.size());
        List reservations = new ArrayList();

        for(ReservationEntity reservationEntity : reservationEntities){
            reservations.add(new ReservationState(reservationEntity.getId(),
                                                  reservationEntity.getBook().getId(),
                                                  reservationEntity.getBook().getTitle().getTitle(),
                                                  reservationEntity.getReserveDate()));
        }

        return reservations;
    }

    public void newReservation(Long[] bookIds, Long memberId) throws NotExistException {
        MemberEntity member = em.find(MemberEntity.class, memberId);
        if(member == null){
            throw new NotExistException(String.format("LoanManagerBean:newReservation: Member (ID: %d) not exists", memberId));
        }

        Set<ReservationEntity> memberReservations = member.getReservations();
        for(Long bookId : bookIds){
            ReservationEntity reservation = new ReservationEntity();
            reservation.create(new Date());
            reservation.setMember(member);

            BookEntity book = em.find(BookEntity.class, bookId);
            reservation.setBook(book);
            memberReservations.add(reservation);

            Set<ReservationEntity> bookReservations = book.getReservations();
            bookReservations.add(reservation);
            book.setReservations(bookReservations);

            em.persist(reservation);
        }

        member.setReservations(memberReservations);
        em.flush();
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void deleteReservations(Long[] reservations) throws NotExistException {
        for (Long id : reservations) {
            ReservationEntity reservation = em.find(ReservationEntity.class, id);
            if(reservation==null){
                em.clear();
                throw new NotExistException(String.format("LoanManagerBean:deleteReservation: Reservation (ID: %d) not exists", id));
            }
            reservation.getBook().getReservations().remove(reservation);
            reservation.getMember().getReservations().remove(reservation);
            em.remove(reservation);
        }
        em.flush();
    }


}
